package figure8;

import java.io.File;
import java.io.IOException;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.Mixer;

import figure8.GUI.EffectWindow.Effect;

public class SignalAlteration {
	
	File file = null;
	File effFile = null;
	
	WavFile wavFile = null;
	WavFile effWavFile = null;
	
    double min = 0; 
    double max = min;
    	
	SignalAlteration(File inFile, Effect eff, double[] settings) throws IOException, WavFileException{

		//file for reading
		this.file = inFile;
		this.wavFile = WavFile.openWavFile(inFile);
		
		//file for writing
		if(inFile.getName().endsWith("effFile1.wav")) {
			this.effFile =  new File(inFile.getParentFile().getParentFile()+File.separator+"Effects"+File.separator+"effFile2.wav");
	
			this.effWavFile = WavFile.newWavFile(effFile.getAbsoluteFile(), wavFile.getNumChannels(), wavFile.getNumFrames(), 
				                                          wavFile.getValidBits(), wavFile.getSampleRate());
		} else {
			if(!(new File(inFile.getParentFile().getParentFile()+File.separator+"Effects").exists()))
				new File(inFile.getParentFile().getParentFile()+File.separator+"Effects").mkdir();				
			this.effFile =  new File(inFile.getParentFile().getParentFile()+File.separator+"Effects"+File.separator+"effFile1.wav");
		
			this.effWavFile = WavFile.newWavFile(effFile.getAbsoluteFile(), wavFile.getNumChannels(), wavFile.getNumFrames(), 
				                                          wavFile.getValidBits(), wavFile.getSampleRate());
		}
    	
		// Read frames into buffer to get min and max amplitudes
        int framesRead = -1;
        double[] buffer = new double[(int)(wavFile.getNumChannels()*100)];
        do {
        	framesRead = wavFile.readFrames(buffer, 100);
           	// Loop through frames and look for minimum and maximum value
        	for (int s=0 ; s < (framesRead * wavFile.getNumChannels()) ; s++) {
        		if (buffer[s] > max) max = buffer[s];
        		if (buffer[s] < min) min = buffer[s];
        	}
        } while (framesRead != 0);
        wavFile.close(); 
        
        // Create a buffer for reading and a buffer for writing
        double[] buffer1 = new double[(int) (wavFile.getNumFrames() * wavFile.getNumChannels())];
        double[] buffer2 = new double[(int) (effWavFile.getNumFrames() * effWavFile.getNumChannels())];
        
        this.wavFile = WavFile.openWavFile(inFile);

        //Effect Operations
        switch(eff){
        
        //PRE_AMP
		//Get a constant to multiply the readings of the input by so that it is possible
		//to set the volume so that it is between 90% and 10% of wav's possible amplitude.
		case PRE_AMP :
			double gainConst = ((settings[1] * 0.8) + 0.1) / (Math.max(max, min));
			double gate = settings[0]*0.2;
			do {
				framesRead = wavFile.readFrames(buffer1, (int) wavFile.getNumFrames());	            
            	for (int s=0 ; s < (framesRead * wavFile.getNumChannels()) ; s++)
            		buffer2[s] = buffer1[s] >= gate? buffer1[s]*0.1 : gainConst * buffer1[s];
        	} while (framesRead != 0);
	    	break;

	    //DISTORTION
		//Clips the original wave by up to half it's original max oscillation making the wave more like a square
		//wave, loosing the data that's clipped and hence introducing extra partials and harmonics to the sound.
	    //If gain = 0 the volume is not boosted after the clipping, if gain  1 the volume is boosted back to
	    //its original max volume (and with it raising the average volume).
		case DISTORTION :
			double distClip = Math.max(max, min)* ( 1 - (settings[0] * 0.5) );
			double gain = settings[1] * Math.max(max, min) / distClip;
			do {
				framesRead = wavFile.readFrames(buffer1, (int) wavFile.getNumFrames());
				for (int s=0 ; s < (framesRead * wavFile.getNumChannels()) ; s++) {
					if(buffer1[s]>0)
						buffer2[s] = buffer1[s] >= distClip ? gain*distClip : gain*buffer1[s];
					else
						buffer2[s] = (-1*buffer1[s]) >= distClip ? -1*gain * distClip : gain*buffer1[s];
				}
			} while (framesRead != 0);
			break;

		//OCTAVIA
	    //
	    //
		case OCTAVIA    :
			double octDistClip = Math.max(max, min)* ( 1 - (settings[0] * 0.8) );
 			do {
				framesRead = wavFile.readFrames(buffer1, (int) wavFile.getNumFrames());
				for (int s=0 ; s < (framesRead * wavFile.getNumChannels()) ; s++) {
					if(buffer1[s]>0)
						buffer2[s] = buffer1[s] >= octDistClip ? octDistClip : buffer1[s];
					else
						buffer2[s] = (-1 * buffer1[s]) >= octDistClip ? octDistClip : -1*buffer1[s];
				}
			} while (framesRead != 0);
			buffer2 = mix(buffer1, buffer2, settings[1]);
			break;

		//DELAY
		//
		//
		case DELAY      :
			framesRead = wavFile.readFrames(buffer1, (int) wavFile.getNumFrames());
			buffer2 = buffer1;
			int bpm = (int)settings[3];
			int rep = (int)(settings[2]*10);
			double decay = settings[1];
			int frameShift = 0;

			frameShift = noteLenToFrames((int)settings[0], bpm);
			
			for(int i = 0; i < rep; i++)
				for(int s1 = 0; s1 < framesRead*wavFile.getNumChannels(); s1++) {
					int buff2pos = (s1+frameShift)%(int)(wavFile.getNumFrames()/wavFile.getNumChannels());
					double amplitude = buffer2[buff2pos] +(decay*buffer1[s1]);
					buffer2[buff2pos] = amplitude > 0.9 || amplitude < -0.9 ? 0.9+(amplitude/10) : amplitude;
				}
			break;
		
		//REVERSE
		//
		//
		case REVERSE    :
			framesRead = wavFile.readFrames(buffer1, (int) wavFile.getNumFrames());
			int bpmr = (int)settings[1];
			int i, p1, p2, fShift;
			p2 = fShift = noteLenToFrames((int)settings[0], bpmr);
			p1 = i = 0;
			
			while(p2 <= wavFile.getNumFrames()*wavFile.getNumChannels()) {
				for(; p2 >= p1 ; p2--, i++)
					if (i < buffer2.length && p2 < buffer1.length) buffer2[i] = buffer1[p2];
				p1 = p1 + fShift;
				p2 = p1 + fShift;
			}
			break;
			
		default         :
			System.err.println("Unrecognised effect in SignalAlteration");
	    }
		
     	effWavFile.writeFrames(buffer2, (int)(effWavFile.getFramesRemaining()));
        wavFile.close();
        effWavFile.close();
        file = effFile;
	}
	
	public File returnFile() { return file; }
	
	public double[] mix(double[] dry, double[] wet, double percent) {
		for(int i = 0; i < wet.length; i++) {
			wet[i] = (1-percent)*dry[i] + percent*wet[i];
		}
		return wet;
	}

	public int noteLenToFrames(int noteLen, int bpm) {
		int frameShift = 0;
		switch(noteLen) {
		case(0)  : frameShift = (int)wavFile.getSampleRate()*60/(8*bpm*wavFile.getNumChannels()); break;
		case(1)  : frameShift = (int)wavFile.getSampleRate()*60*3/(16*bpm*wavFile.getNumChannels()); break;
		case(2)  : frameShift = (int)wavFile.getSampleRate()*60/(4*bpm*wavFile.getNumChannels()); break;
		case(3)  : frameShift = (int)wavFile.getSampleRate()*60*3/(8*bpm*wavFile.getNumChannels()); break;
		case(4)  : frameShift = (int)wavFile.getSampleRate()*60/(2*bpm*wavFile.getNumChannels()); break;
		case(5)  : frameShift = (int)wavFile.getSampleRate()*60*3/(4*bpm*wavFile.getNumChannels()); break;
		case(6)  : frameShift = (int)wavFile.getSampleRate()*60/(bpm*wavFile.getNumChannels()); break;
		case(7)  : frameShift = (int)wavFile.getSampleRate()*60*3/(2*bpm*wavFile.getNumChannels()); break;
		case(8)  : frameShift = (int)wavFile.getSampleRate()*60*2/(bpm*wavFile.getNumChannels()); break;
		case(9)  : frameShift = (int)wavFile.getSampleRate()*60*3/(1*bpm*wavFile.getNumChannels()); break;
		case(10) : frameShift = (int)wavFile.getSampleRate()*60*4/(bpm*wavFile.getNumChannels()); break;
		}
		return (int)Math.floor(frameShift);
	}

}
